package de.Mjpeg;

import java.io.IOException;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;

public class MjpegView extends SurfaceView implements SurfaceHolder.Callback {
    
	public static Button btn_cams_back;
	public static int NumCameras = 3;
	public static int NumConnected = 0;
	public static String[] Hostnames = {"","",""};
	public static String[] Passwords = {"","",""};
	public static String[] Usernames = {"","",""};
	public static String[] Types = {"","",""};
	public static DefaultHttpClient[] HttpClients = {null,null,null};
	public static MjpegView[] Mvs = {null,null,null};
	public static int BigCamId = -1;
	
	private static boolean[] bll_connected = {false,false,false};
	
	//small dims should match those set in the main.xml file
	private static int cam_width_small = 190;
	private static int cam_height_small = 144;
	private static int cam_width_big = 575;
	private static int cam_height_big = 400;
	
    public String URL = "";
    public int Id = -1;
    private MjpegViewThread thread;
    private SurfaceHolder holder;
    private MjpegInputStream mIn = null;
    
	private enum PanTiltDir{
		TL, T, TR, L, C, R, BL, B, BR, None
	}
	private PanTiltDir last_pt_dir = PanTiltDir.None;
    private boolean bll_stream_active = false;
    private boolean surfaceCreated = false;    
    private Paint overlayPaint;

    /**********************
	Static methods
    ***********************/
    public static int ConnectAll(){
		
		MjpegView mv;
		int i;
		boolean result;
		
		if(NumConnected == NumCameras ) return NumConnected;
		
		//connect to all the disconnected cameras
		NumConnected = 0;
		for(i=0;i<NumCameras;i++){
			mv = Mvs[i];
			if(mv != null){
				result = mv.Connect();
				
				//returns true if it is on
				if(result == true){
					NumConnected++;
				}
				bll_connected[i] = result;
				
				//we tested if each stream is good
				//now, if we are only looking @ one camera, kill the other threads so they don't use resources
				if(result = true && BigCamId != -1 && BigCamId != i){
					mv.StopStream();
				}
			}
		}
	
		// Return number connected
		return NumConnected;
	}

	public static void DisconnectAll() {
		
		int i;
		MjpegView mv;
		
		// Stop the video streams
		for(i=0;i<NumCameras;i++){
			mv = Mvs[i];
			if(mv != null){
				mv.Disconnect();
			}
			bll_connected[i] = false;
		}
		NumConnected = 0;
		
	}

	/*
	This is called when we get back to the main menu (either from settings or if we exited the program).
	The MjpegViews lose their connection state and hostnames. 
	*/
	public static void UpdateMvProps(){
		
		int i;
		MjpegView mv;
		
		for(i=0;i<NumCameras;i++){
			mv = Mvs[i];
			if(mv != null){
				mv.URL = get_connection_url(i);
			}
		}
	}
	
	//they clicked the camera "cam_id"
	public static void MakeCamBig(int cam_id){
		
		int i;
		MjpegView mv_tmp;
		LinearLayout.LayoutParams lp;
		
		//big video size
		lp = new LinearLayout.LayoutParams(cam_width_big,cam_height_big);
		for(i=0;i<NumCameras;i++){
			mv_tmp = Mvs[i];
			//stop streams
			if(bll_connected[i] == true){
				mv_tmp.StopStream();
			}
			if(i != cam_id){
				//didn't click this camera
				//hide it
				mv_tmp.setVisibility(View.GONE);
			}else{
				//clicked this camera
				//make it big
				//l,t,r,b
				lp.setMargins(0, 0, 0, 0);
				mv_tmp.setLayoutParams(lp);
				mv_tmp.setOnClickListener(null);
				mv_tmp.setOnTouchListener(streamTouchListener);
				mv_tmp.last_pt_dir = PanTiltDir.C;
			}
		}
		BigCamId = cam_id;

		//show the back button, make it clickable
		btn_cams_back.setVisibility(View.VISIBLE);
		btn_cams_back.setOnClickListener(backCamListener);
		
	}
	
	//they clicked the back button to make a camera small again
	public static void MakeCamSmall(){
		
		int i;
		MjpegView mv_tmp;
		LinearLayout.LayoutParams lp;
		
		if(BigCamId != -1){
			//one is actually big
			lp = new LinearLayout.LayoutParams(cam_width_small,cam_height_small);
			for(i=0;i<NumCameras;i++){
				mv_tmp = Mvs[i];
				if(i == BigCamId){
					
					//this is the big camera
					//stop stream
					if(bll_connected[i] == true){
						mv_tmp.StopStream();
					}
					//resize it, set margins
					if(i == NumCameras -1){
						//the highest id camera does not have any margins
						lp.setMargins(0, 0, 0, 0);
					}else{
						//other cams have a 5px right margin
						lp.setMargins(0, 0, 5, 0);
					}
					mv_tmp.setLayoutParams(lp);
					
					//allow to click and make big
					mv_tmp.setOnClickListener(streamClickListener);
					mv_tmp.setOnTouchListener(null);
				}else{
					//this is a small camera
					//make it visible, start the stream
					mv_tmp.setVisibility(View.VISIBLE);
				}
			}
		}
		BigCamId = -1;
	}
	
	
	//they want to look at all the cameras
	public static OnClickListener backCamListener = new OnClickListener() {
		public void onClick(View v) {
			
			Log.d("back","clicked back btn");
			
			//hide the back button, stop it from clicking
			btn_cams_back.setVisibility(View.GONE);
			btn_cams_back.setOnClickListener(null);
			
			MakeCamSmall();
			
		}
	};
	

    public static OnClickListener streamClickListener = new OnClickListener() {
		public void onClick(View v) {
			
			MjpegView mv;
			
			mv = (MjpegView)v;
			
			//clicked a small stream, make it big
			Log.d("stream clicked", "clicked a stream w/ id " + mv.Id);
			
			MakeCamBig(mv.Id);
			
			return;
		}
    };
    
    public static View.OnTouchListener streamTouchListener = new View.OnTouchListener(){
    	
    	@Override
    	public boolean onTouch(View v, MotionEvent event){
		
			MjpegView mv;
			mv = (MjpegView)v;
			
			int action = event.getAction();
			float x,y;
			boolean touching;
			
			Log.d("stream touched", "touched a stream w/ id " + mv.Id);
			
			x = 0;
			y = 0;
			
			switch(action){
			case MotionEvent.ACTION_MOVE:
				x = event.getX();
				y = event.getY();
				touching = true;
				break;
			case MotionEvent.ACTION_DOWN:
				x = event.getX();
				y = event.getY();
				touching = true;
				break;
			case MotionEvent.ACTION_UP:
				touching = false;
				break;
			default:
				touching = false;
				break;
			}
			
			if(touching == true){
				Log.d("stream touched", "x y = " + x + " " + y);
				mv.pan_tilt(x,y);
			}else{
				//reset the cursor
				Log.d("stream touched", "STOP");
				mv.pan_tilt_stop();
			}
		
			return true;
	
		}
    };
	
	/*
	Constructs a hostname for the ith camera from the settings. 
	*/
	private static String get_connection_url(int i){
		
		String loc_hostname;
		
		loc_hostname = "";
		
		if(Types[i].equals("Foscam")){
			loc_hostname = "http://" + Hostnames[i] + "/videostream.cgi?user=" + Usernames[i] + "&pwd=" + Passwords[i];
		}else if(Types[i].equals("TP-Link")){
			loc_hostname = "http://" + Hostnames[i] + "/video.mjpg";
		}else if(Types[i].equals("Brickcom")){
			if(Usernames[i] != "" && Passwords[i] != ""){
				loc_hostname = "http://" + Usernames[i] + ":" + Passwords[i] + "@" + Hostnames[i];
			}else{
				//no account info
				loc_hostname = "http://" + Hostnames[i];
			}
		}
		
		//TODO testing
		/*
		if(i == 1){
			loc_hostname = "http://ftpcontent.worldnow.com/wtvf/NC5wx/skynet/images/clarksville/sky_clarksville_sm.jpg";
		}
		*/
		
		return loc_hostname;
		
	}
    
	/*
	Constructs a hostname for the ith camera's pan tilt controls 
	*/
	private static String get_pt_url(int i,PanTiltDir pt_dir){
		
		String loc_url;
		String str_dir_fos, str_dir_tp;
		String str_pt_home;
		
		loc_url = "http://" + Hostnames[i];
		
		str_dir_fos = str_dir_tp = "";
		str_pt_home = "";
		
		if(Types[i].equals("Foscam")){
			loc_url += "/decoder_control.cgi?command=";
		}else if(Types[i].equals("TP-Link")){
			loc_url += "/cgi-bin/operator/ptzset?";
		}else{
			return "";
		}
			
		switch(pt_dir){
			case TL:
				str_dir_tp = "upleft";
				str_dir_fos = "91";
				break;
			case TR:
				str_dir_tp = "upright";
				str_dir_fos = "90";
			case T:
				str_dir_tp = "up";
				str_dir_fos = "0";
				break;
			case BL:
				str_dir_tp = "downleft";
				str_dir_fos = "93";
				break;
			case BR:
				str_dir_tp = "downright";
				str_dir_fos = "92";
			case B:
				str_dir_tp = "down";
				str_dir_fos = "2";
				break;
			case L:
				str_dir_tp = "left";
				str_dir_fos = "6";
				break;
			case R:
				str_dir_tp = "right";
				str_dir_fos = "4";
				break;
			case C:
				if(Types[i].equals("Foscam")){
					//foscam does not have a center
					return "";
				}else if(Types[i].equals("TP-Link")){
					str_pt_home = "gotoserverpresetname=Preset1";
				}
				break;
			case None:
				//stop
				str_dir_tp = "stop";
				str_dir_fos = "1";
				break;
			default:
				return "";
		}
		
		if(Types[i].equals("Foscam")){
			//construct foscam command			
			loc_url += str_dir_fos;
			
		}else if(Types[i].equals("TP-Link")){
			
			//construct tp-link command
			//different command formats for home, stop, and directional
			if(pt_dir == PanTiltDir.C){
				//go to home
				loc_url += str_pt_home;
			}else if(pt_dir == PanTiltDir.None){
				//stop
				loc_url += "move=" + str_dir_tp;
			}else{
				//directional move
				loc_url += "move=" + str_dir_tp + "&move=repeat";
			}
		}
		
		return loc_url;
		
	}
	
    /*
    The user wants to see the stream.
    This is different from StartStream() where we are actually starting to show the stream.
    Return true if the stream gets turned on or was already turned on
    */
    public boolean Connect(){
    	
    	if(bll_connected[Id] == false){

			//this camera is disconnected, try to connect
    		bll_connected[Id] = StartStream();
			
			return bll_connected[Id];
				
    	}else{
    		//already on
    		return true;
    	}

    }
    
    /*
    The user doesn't want it to stream anymore.
    */
    public void Disconnect(){
    	
    	if(bll_connected[Id] == true){
    		StopStream();
    		bll_connected[Id] = false;
    	}
    	
    }
    
    //constructors
    public MjpegView(Context context, AttributeSet attrs) {
    	super(context, attrs);
    	link_holder(context);
    }
    public MjpegView(Context context, AttributeSet attrs, int def_style) {
    	super(context, attrs,def_style);
    	link_holder(context);
    }
    public MjpegView(Context context) {
    	super(context);
    	link_holder(context);
    } 
    
    public void surfaceChanged(SurfaceHolder holder, int f, int w, int h) { 
    	Log.d("resized surface", "sfc resized");
    	surfaceCreated = true;
    	if(bll_connected[Id] == true){
    		StartStream();
    	}
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
    	Log.d("surface destroyed", "bll_connected[Id] = " + bll_connected[Id]);
    	surfaceCreated = false;
    	if(bll_connected[Id] == true){
    		StopStream();
    	}
    }
     
    public void surfaceCreated(SurfaceHolder holder) {
    }

    private void link_holder(Context con) {
		con = getContext();
	    holder = getHolder();
	    holder.addCallback(this);
    }
    
    /*
    Start a thread that displays the stream.
	Returns whether the stream is active.
    */
    public boolean StartStream() {
    	Context con;
    	
    	if(bll_stream_active == false){
    		
			mIn = MjpegInputStream.read(URL,Usernames[Id],Passwords[Id]);
			if(mIn != null){
				
				//connected to host
		    	con = getContext();
		        holder.addCallback(this);
		        thread = new MjpegViewThread(holder, con);
		        
		        setFocusable(true);
		        overlayPaint = new Paint();
		        overlayPaint.setTextAlign(Paint.Align.LEFT);
		        overlayPaint.setTextSize(12);
		        overlayPaint.setTypeface(Typeface.DEFAULT);
				
		        bll_stream_active = true;
				thread.start();
			}
	        
    	}
    	
    	return bll_stream_active;
    }
    
    /*
    Delete the thread that is displaying the stream (if it exists). 
    */
    public void StopStream() {
    	
    	boolean retry = true;
    	
    	if(bll_stream_active == true){
    		if(BigCamId == Id){
    			pan_tilt_stop();
    		}
    		bll_stream_active = false;
	        while(retry) {
	            try {
	                thread.join();
	                retry = false;
	            } catch (InterruptedException e) {}
	        }
    	}
    }
    
    private void pan_tilt(float x, float y){
    	
    	PanTiltDir new_pt_dir;
    	int x1, x2, y1, y2;
    	String str_url_cmd;
    	
    	/*
		Visualize the 9 cells like this:
		      x1   x2
		 _______________
		|     |    |    |
		|     |    |    |
		|_____|____|____| y1
		|     |    |    |
		|_____|____|____| y2
		|     |    |    |
		|     |    |    |
		|_____|____|____|
		
    	*/
    	x1 = cam_width_big / 3;
    	x2 = 2*x1;
    	y1 = cam_height_big /3;
    	y2 = 2*y1;

    	//determine which pan-tilt direction we are currently in based on x and y
    	if(x<x1){
    		//left
    		if(y < y1){
    			//top
    			new_pt_dir = PanTiltDir.TL;
    		}else if(y > y2){
    			//bottom
    			new_pt_dir = PanTiltDir.BL;
    		}else{
    			//center
    			new_pt_dir = PanTiltDir.L;
    		}
    	}else if(x>x2){
    		//right
    		if(y < y1){
    			//top
    			new_pt_dir = PanTiltDir.TR;
    		}else if(y > y2){
    			//bottom
    			new_pt_dir = PanTiltDir.BR;
    		}else{
    			//center
    			new_pt_dir = PanTiltDir.R;
    		}
    	}else{
    		//center
    		if(y < y1){
    			//top
    			new_pt_dir = PanTiltDir.T;
    		}else if(y > y2){
    			//bottom
    			new_pt_dir = PanTiltDir.B;
    		}else{
    			//center
    			new_pt_dir = PanTiltDir.C;
    		}
    	}
    	
    	if(new_pt_dir != last_pt_dir){
    		//move camera in this new direction
    		Log.d("pan tilt", "new direction: " + new_pt_dir);
    		last_pt_dir = new_pt_dir;
    		
    		//get the command
    		str_url_cmd = get_pt_url(Id, new_pt_dir);
    		
    		//send the command
    		if(str_url_cmd != ""){
	    		try {
	    			HttpClients[Id].execute(new HttpGet(str_url_cmd));
				} catch (ClientProtocolException e) {
				} catch (IOException e) {
				}
    		}
    	}
    	
    }
    
  //stop the pan tilt
    private void pan_tilt_stop(){
    	
    	String str_url_cmd;
    	
    	Log.d("pan tilt", "STOP");
    	
    	last_pt_dir = PanTiltDir.None;
    	
    	str_url_cmd = get_pt_url(Id, last_pt_dir);
    	
    	//send the command
    	if(str_url_cmd != ""){
	    	try {
	    		HttpClients[Id].execute(new HttpGet(str_url_cmd));
			} catch (ClientProtocolException e) {
			} catch (IOException e) {
			}
    	}
    }
    
    /**********************
	Thread
    ***********************/
    public class MjpegViewThread extends Thread {
    	
        private SurfaceHolder mSurfaceHolder;
         
        public MjpegViewThread(SurfaceHolder surfaceHolder, Context context) {
        	mSurfaceHolder = surfaceHolder;
        }

        public void run() {
            Bitmap bm;
            Rect destRect;
            Canvas c = null;
            Paint p = new Paint();
            while (bll_stream_active == true) {
                if(surfaceCreated) {
                    try {
                        c = mSurfaceHolder.lockCanvas();
                        synchronized (mSurfaceHolder) {
                            try {
                                bm = mIn.readMjpegFrame();
                                destRect = getHolder().getSurfaceFrame();
                                c.drawBitmap(bm, null, destRect, p);
                            } catch (IOException e) {}
                        }
                    } finally { if (c != null) mSurfaceHolder.unlockCanvasAndPost(c); }
                }
            }
            
            //set the camera bg to black when we stop the display
            c = mSurfaceHolder.lockCanvas();
            c.drawColor(Color.BLACK);
            mSurfaceHolder.unlockCanvasAndPost(c);
        }
    }
    
}